/**
 * 
 */
package com.swg.cocobox.client;

import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.swg.cocobox.KeyChange;
import com.swg.cocobox.ResponseHandler;

/**
 * @author satriaprayoga
 *
 */
public abstract class ReadWriteClient extends WriterClient {
	
	protected Map<SocketChannel, ResponseHandler> responseMap=Collections.synchronizedMap(new HashMap<SocketChannel, ResponseHandler>());
	
	protected ByteBuffer readBuffer=ByteBuffer.allocate(8192);
	protected InetAddress address;
	
	protected int port;

	public ReadWriteClient(InetAddress address,int port) throws IOException {
		super();
		this.address=address;
		this.port=port;
	}
	
	@Override
	public void run() {
		while(true){
			try {
				synchronized (keyChanges) {
					Iterator<KeyChange> keyIterator=keyChanges.iterator();
					while(keyIterator.hasNext()){
						KeyChange kc=keyIterator.next();
						switch (kc.getType()) {
						case KeyChange.CHANGE:
							SelectionKey key=kc.getChannel().keyFor(selector);
							key.interestOps(kc.getOperation());
							break;
						case KeyChange.REGISTER:
							kc.getChannel().register(selector, kc.getOperation());
							break;
						}
					}
					keyChanges.clear();
				}
				selector.select();
				Iterator<SelectionKey> selectedKeys=selector.selectedKeys().iterator();
				while(selectedKeys.hasNext()){
					SelectionKey key=selectedKeys.next();
					selectedKeys.remove();
					if(!key.isValid()){
						continue;
					}
					if(key.isConnectable()){
						finishConnection(key);
					}else if(key.isReadable()){
						read(key);
					}else if(key.isWritable()){
						write(key);
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	
	protected void read(SelectionKey key)throws IOException{
		SocketChannel socketChannel = (SocketChannel) key.channel();
		this.readBuffer.clear();
		int numRead;
		try {
			numRead = socketChannel.read(this.readBuffer);
		} catch (IOException e) {
			key.cancel();
			socketChannel.close();
			return;
		}
		if (numRead == -1) {
			key.channel().close();
			key.cancel();
			return;
		}
		handleResponse(socketChannel, this.readBuffer.array(), numRead);
	}
	
	@Override
	protected void write(SelectionKey key)throws IOException{
		SocketChannel socketChannel = (SocketChannel) key.channel();
		synchronized (pendingData) {
			List<ByteBuffer> queue = pendingData.get(socketChannel);
			while (!queue.isEmpty()) {
				ByteBuffer buf = (ByteBuffer) queue.get(0);
				socketChannel.write(buf);
				if (buf.remaining() > 0) {
					break;
				}
				queue.remove(0);
			}

			if (queue.isEmpty()) {
				key.interestOps(SelectionKey.OP_READ);
			}
		}
	}
	
	protected abstract void handleResponse(SocketChannel socketChannel, byte[] data, int numRead)throws IOException;

}
